﻿using System;
using System.Collections.Generic;
using System.Linq;
using Krs.Ats.IBNet;
using Systemathics.FrameWork;
using Systemathics.FrameWork.Interfaces;
using Order = Systemathics.FrameWork.Order;

namespace Systemathics.Providers
{
    [BrokerFees("IB")]
    [ProviderName("IB")]
    public partial class IB : IOrderProvider
    {
        private const string                                            ACCOUNT_CODE = "";
        private const string                                            HOST = "127.0.0.1";
        private const int                                               PORT = 7495;
        private const int                                               CLIENT_ID = 0;
        private const int                                               BOND_DEPTH = 1;
        private const int                                               ETF_DEPTH = 1;
        private const int                                               FUTURE_DEPTH = 1;
        private const int                                               OPTION_DEPTH = 1;                
        private const int                                               INDEX_DEPTH = 1;                
        private const int                                               CASH_DEPTH = 1;

        private static IBClient                                         client;
        private static int                                              NextOrderId;
        private bool                                                    isConnected;
        private readonly Dictionary<int, LastFeed>                      LastFeeds;
        private readonly Dictionary<Guid, int>                          Orders;
        private readonly Dictionary<Guid, Contract>                     Contracts;
        private SystemathicsEntities                                    db;

        private readonly Dictionary<int, String>                        CurrenciesToUpdate;
        private readonly Dictionary<String, bool>                       CurrenciesCounter;
        private readonly Dictionary<int, BarSeries>                     HistoricalRequests;

        private readonly Dictionary<Guid, OrderTracker>                 SystemathicsOrders;
        private int                                                     FeedSuscriptionNumber;
        private string                                                  PROVIDER_NAME;

        private event EventHandler<BarSeries>                           _EmitBarSeries;
        private event EventHandler<TradeSeries>                         _EmitTradeSeries;
        private event EventHandler<QuoteSeries>                         _EmitQuoteSeries;
        private event EventHandler<Trade>                               _EmitNewTrade;
        private event EventHandler<Blotter>                             _EmitNewBlotter;
        private event EventHandler<MarketData>                          _EmitNewMarketData;
        private event EventHandler<FireMessageEventArgs>                _EmitConnectionStatus;
        private event EventHandler<FireMessageEventArgs>                _EmitError;
        private event EventHandler<FrameWork.OrderStatusEventArgs>      _EmitOrderStatus;
        private event EventHandler<OrderExectutionEventArgs>            _EmitOrderExecution;

        #region Financial Advisor

        private readonly FADataType                       AllocType;
        private readonly FinancialAdvisorAllocationMethod AllocMethod;
        private readonly String                           AllocAccount;
        private readonly String                           AllocGroup;
        private readonly String                           AllocProfile;

        #endregion

        public IB()
        {
            var attribute = Attribute.GetCustomAttributes(GetType(), typeof (ProviderName))[0] as ProviderName;
            PROVIDER_NAME = attribute.Name;
            //Broker = PROVIDER_NAME;
            NextOrderId = 0;
            FeedSuscriptionNumber = 0;
            isConnected = false;
            LastFeeds = new Dictionary<int, LastFeed>();
            Orders = new Dictionary<Guid, int>();
            Contracts = new Dictionary<Guid, Contract>();
            HistoricalRequests = new Dictionary<int, BarSeries>();
            CurrenciesToUpdate = new Dictionary<int , String>();
            CurrenciesCounter = new Dictionary<String , bool>();
            SystemathicsOrders = new Dictionary<Guid, OrderTracker>();
            AllocType = FADataType.Undefined;
            AllocMethod = FinancialAdvisorAllocationMethod.None;
            AllocAccount = String.Empty;
            AllocGroup = String.Empty;
            AllocProfile = String.Empty;
            var sa = new[] { "USD", "JPY", "GBP", "HKD", "AUD", "CAD", "MXN", "NOK", "NZD", "SEK", "SGD", "CHF" };
            var start = 388580;
            foreach (string s in sa)
            {
                CurrenciesToUpdate[start] = s;
                CurrenciesCounter[s] = false;
                start++;
            }
        }

        #region IOrderProvider Members

        private void ApplyAccountInformation(ref Krs.Ats.IBNet.Order order)
        {
            switch (AllocType)
            {
                case FADataType.Groups:
                    order.FAGroup = AllocGroup;
                    order.FAMethod = AllocMethod;
                    break;
                case FADataType.Profiles:
                    order.FAProfile = AllocProfile;
                    break;
                case FADataType.Aliases:
                    order.Account = AllocAccount;
                    break;
            }
        }

        bool IOrderProvider.Send(Order o)
        {
            lock (o)
            {
                // client.RequestIds(1);
                // Time to update NextrderId may be to low generating error 2012
                // du coup, en catchant cette erreur tu rejetais, un ordre, qui etait deja executé. en plus tu ecrasais Orders[o.Id] avec un id qui appartenanit en fait a un ordre precedant.

                NextOrderId++;
                Orders[o.Id] = NextOrderId;
                SystemathicsOrders[o.Id] = new OrderTracker(o);

                try
                {
                    var krsOrder = new Krs.Ats.IBNet.Order
                    {
                        Action = (o.Side == OrderSide.Buy) ? ActionSide.Buy : ActionSide.Sell,
                        OutsideRth = o.Ext.OutsideRth,
                        TotalQuantity = o.TargetQuantity,
                        OrderType = (OrderType)Enum.Parse(typeof(OrderType), o.Style.ToString()),
                        AllOrNone = o.Ext.AllOrNone,
                        LimitPrice = o.TargetPrice,
                        Tif = (Krs.Ats.IBNet.TimeInForce)Enum.Parse(typeof(Krs.Ats.IBNet.TimeInForce), o.Ext.Tif.ToString()),
                        OrderRef = o.Id.ToString()
                    };

                    //if you are FA
                    ApplyAccountInformation(ref krsOrder);
                    client.PlaceOrder(Orders[o.Id], Contracts[o.InstrumentId], krsOrder);
                    return true;
                }
                catch (Exception ex)
                {
                    _EmitError(this, new FireMessageEventArgs(ex, Time.Now.TimeOfDay));
                    SystemathicsOrders.Remove(o.Id);
                }
                return false;                
            }
        }
        bool IOrderProvider.Cancel(Order o)
        {
            if (Orders.ContainsKey(o.Id))
            {
                client.CancelOrder(Orders[o.Id]);
                return true;
            }
            return false;
        }
        bool IOrderProvider.Modify(Order o)
        {
            return false;
        }

        event EventHandler<FrameWork.OrderStatusEventArgs> IOrderProvider.EmitOrderStatus
        {
            add { _EmitOrderStatus += value; }
            remove { _EmitOrderStatus -= value; }
        }
        event EventHandler<OrderExectutionEventArgs> IOrderProvider.EmitOrderExecution
        {
            add { _EmitOrderExecution += value; }
            remove { _EmitOrderExecution -= value; }
        }

        #endregion

        #region IB's Order CallBacks

        private static void UpdatePortfolio(object sender, UpdatePortfolioEventArgs e)
        {
            //check official positions for all instruments and plateform positions
            // if error matching: add to DBOrder DBStatus et DBExec etc...
            //if matching error then check if order exist, if yes add exec automatically in db !!!!!!!
        }
        private static void UpdateAccountValue(object sender, UpdateAccountValueEventArgs e)
        {
            if (e.Key == "ExchangeRate")
            {
                using (var dba = new SystemathicsEntities(GlobalVariables.EntitiesConnectionString()))
                {
                    if (dba.DBCurrency.Where(u => u.Ticker == e.Currency).Any())
                    {
                        dba.DBCurrency.Where(y => y.Ticker == e.Currency).FirstOrDefault().ReferencePrice = double.Parse(e.Value,GlobalVariables.SystemathicsCulture());
                        dba.SaveChanges(true);
                    }
                }
            }
            else if (e.Value.CompareTo("0.0") != 0)
            {
                // At starting Check Cash Equity plateform and IB
                // Faire un recapitulatif style pdf un truc comme ca
            }
        }
        private void OpenOrder(object sender, OpenOrderEventArgs e)
        {
            if (e.Order.OrderRef != null)
            {
                var id = new Guid(e.Order.OrderRef);
                Orders[id] = e.OrderId;
            }
            else
            {
                _EmitError(this, 
                           new FireMessageEventArgs(
                               "Request All Open Orders: this Order hasn't been sent by the systemathics platefform: No OrderRef",
                               Time.Now.TimeOfDay));
                _EmitError(this,
                           new FireMessageEventArgs(
                               "Symbol: " + e.Contract.Symbol + " Side :" + e.Order.Action + " Qty :" +
                               e.Order.TotalQuantity + " Status: " + e.OrderState.Status + " Style: " +
                               e.Order.OrderType, Time.Now.TimeOfDay));
            }
        }
        private void ExecutionDetails(object sender, ExecDetailsEventArgs e)
        {
            if (Orders.ContainsValue(e.OrderId))
            {
                var id = Orders.Where(u => u.Value == e.OrderId).FirstOrDefault().Key;
                _EmitOrderExecution(this, new OrderExectutionEventArgs(id, new decimal(e.Execution.Price), e.Execution.Shares, "IB Exec Id: " + e.Execution.ExecutionId));
            }
        }
        private void OrderStatus(object sender, Krs.Ats.IBNet.OrderStatusEventArgs e)
        {
            if (Orders.ContainsValue(e.OrderId))
            {
                var id = Orders.Where(u => u.Value == e.OrderId).FirstOrDefault().Key;
                SystemathicsOrders[id].SetRemainingQty(e.Remaining);
                switch (e.Status)
                {
                    //case Krs.Ats.IBNet.OrderStatus.PendingSubmit:
                    //    break;
                    //case Krs.Ats.IBNet.OrderStatus.PendingCancel:
                    //    break;
                    //case Krs.Ats.IBNet.OrderStatus.PreSubmitted:
                    //    break;
                    //case Krs.Ats.IBNet.OrderStatus.PartiallyFilled:
                    //    break;
                    //case Krs.Ats.IBNet.OrderStatus.Filled:
                    //  break;
                    //case Krs.Ats.IBNet.OrderStatus.None:
                    //    break;
                    case Krs.Ats.IBNet.OrderStatus.Submitted:
                        var submited = new Systemathics.FrameWork.OrderStatusEventArgs(id) { Status = Systemathics.FrameWork.OrderStatus.ACK, RemainingQty = e.Remaining };
                        _EmitOrderStatus(this, submited);
                        break;
                    case Krs.Ats.IBNet.OrderStatus.Canceled: 
                        var canceled = new Systemathics.FrameWork.OrderStatusEventArgs(id){ Status = Systemathics.FrameWork.OrderStatus.ACK_CANCEL, CanceledQty = e.Remaining, RemainingQty = e.Remaining};
                        _EmitOrderStatus(this, canceled);
                        break;
                    case Krs.Ats.IBNet.OrderStatus.Inactive:
                        var reject = new FrameWork.OrderStatusEventArgs(id)
                        {
                            Status = FrameWork.OrderStatus.REJECT,
                            RemainingQty = e.Remaining,
                            RequestedQty = SystemathicsOrders[id].TargetQuantity,
                            RequestedPrice = SystemathicsOrders[id].TargetPrice,
                            Reason = ErrorMessage.FailSendOrder.ToString()
                        };
                        _EmitOrderStatus(this, reject);
                        _EmitError(this, new FireMessageEventArgs("REJECT : Inactive - Order Id: " + id, Time.Now.TimeOfDay));
                        break;
                }
            }
        }
        private void Error(object sender, ErrorEventArgs e)
        {
            if (Orders.ContainsValue(e.TickerId))
            {
                if (e.ErrorCode == ErrorMessage.FailSendCancelOrder)
                {
                    var id = Orders.Where(u => u.Value == e.TickerId).FirstOrDefault().Key;
                    _EmitOrderStatus(this, new FrameWork.OrderStatusEventArgs(id, FrameWork.OrderStatus.REJECT_CANCEL,
                                                              SystemathicsOrders[id].TargetQuantity,
                                                              SystemathicsOrders[id].TargetPrice,
                                                              0,
                                                              SystemathicsOrders[id].RemainingQuantity,
                                                              ErrorMessage.FailSendCancelOrder.ToString()));
                }
                else if (e.ErrorCode == ErrorMessage.FailSendOrder || e.ErrorCode == ErrorMessage.NoValidId) 
                {
                    var id = Orders.Where(u => u.Value == e.TickerId).FirstOrDefault().Key;
                    _EmitOrderStatus(this, new FrameWork.OrderStatusEventArgs(id, FrameWork.OrderStatus.REJECT, SystemathicsOrders[id].TargetQuantity,
                                                                                                                SystemathicsOrders[id].TargetPrice,
                                                                                                                SystemathicsOrders[id].RemainingQuantity,
                                                                                                                0, ErrorMessage.FailSendOrder.ToString()));
                }
            }
            else
            {
                if (e.ErrorCode == ErrorMessage.NotConnected) //1001 ?
                {
                    if (isConnected)
                    {
                        _EmitConnectionStatus(this, new FireMessageEventArgs(ConnectionStatus.Disconnected, Time.Now.TimeOfDay));
                        _EmitError(this, new FireMessageEventArgs(e.ErrorMsg, Time.Now.TimeOfDay));
                    }
                }
                else if ((int)e.ErrorCode == 200)
                {

                    if (LastFeeds.ContainsKey(e.TickerId))
                    {
                        _EmitError(this, new FireMessageEventArgs(e.ErrorMsg + ": UnSuscribe " + LastFeeds[e.TickerId].Blotter.Id, Time.Now.TimeOfDay));
                        LastFeeds.Remove(e.TickerId);                        
                    }
                }
                else
                {
                    if (LastFeeds.ContainsKey(e.TickerId) && LastFeeds[e.TickerId].Trade.Id != Guid.Empty)
                        _EmitError(this, new FireMessageEventArgs(LastFeeds[e.TickerId].Trade.Id + " :" + e.ErrorMsg, Time.Now.TimeOfDay));
                    else
                        _EmitError(this, new FireMessageEventArgs("Ticker/Order Id: " + e.TickerId + " - Error:" + e.ErrorMsg, Time.Now.TimeOfDay));
                }
            }
        }

        #endregion IB's Order CallBacks

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            client.Dispose();
        }

        #endregion
    }
}